package pl.enigmatic.math;

import java.util.Collection;

import javax.vecmath.AxisAngle4d;
import javax.vecmath.Quat4d;
import javax.vecmath.Vector3f;


public class MathUtil {

	private MathUtil() {}

	public static Quat4d eulerToQuat(final double x, final double y, final double z) {
		final Quat4d qX = new Quat4d(Math.sin(x / 2), 0, 0, Math.cos(x / 2));
		final Quat4d qY = new Quat4d(0, Math.sin(y / 2), 0, Math.cos(y / 2));
		final Quat4d qZ = new Quat4d(0, 0, Math.sin(z / 2), Math.cos(z / 2));
		qX.mul(qX, qY);
		qX.mul(qX, qZ);
		return qX;
	}

	public static AxisAngle4d quatToAxisAngle(final Quat4d q) {
		return new AxisAngle4d(q.x, q.y, q.z, 2 * Math.acos(q.w));
	}

	public static AxisAngle4d eulerToAxisAngle(final double x, final double y, final double z) {
		return quatToAxisAngle(eulerToQuat(x, y, z));
	}

	/**
	 * Returns non-negative remainder from division of <code>num</code> by <code>modulus</code>. If the <code>modulus</code> is <code>0</code> -
	 * returns <code>0</code>. If <code>modulus</code> is negative - gets its absolute value instead.
	 * @param num number to get remainder from
	 * @param modulus the modulus value
	 * @return remainder from set <code>{0, 1, ..., modulus-1}</code>
	 */
	public static int mod(final int num, final int modulus) {
		if (modulus == 0) { return 0; }
		int modul = modulus;
		if (modul < 0) {
			modul *= -1;
		}
		return (num % modul + modul) % modul;
	}

	/**
	 * Finds the minimal value from all the given ones.
	 * @param X values to investigate
	 * @return minimal value from the given ones
	 */
	public static int min(final int... X) {
		int res = Integer.MAX_VALUE;
		for (final int x : X) {
			if (x < res) {
				res = x;
			}
		}
		return res;
	}

	/**
	 * Finds the maximal value from all the given ones.
	 * @param X values to investigate
	 * @return maximal value from the given ones
	 */
	public static int max(final int... X) {
		int res = Integer.MIN_VALUE;
		for (final int x : X) {
			if (x > res) {
				res = x;
			}
		}
		return res;
	}

	/**
	 * Clamps the given <code>x</code> value to the given <code>left</code> limit.
	 * @param left the left/bottom limit value
	 * @param x the value to be clamped
	 * @return <code>x</code> or <code>left</code>
	 */
	public static int clampBottom(final int left, final int x) {
		return x < left ? left : x;
	}

	/**
	 * Clamps the given <code>x</code> value to the given <code>right</code> limit.
	 * @param right the right/top limit value
	 * @param x the value to be clamped
	 * @return <code>x</code> or <code>right</code>
	 */
	public static int clampTop(final int right, final int x) {
		return x > right ? right : x;
	}

	/**
	 * Clamps the given <code>x</code> value to the given <code>left</code> and <code>right</code> limits.
	 * @param left the left/bottom limit value
	 * @param right the right/top limit value
	 * @param x the value to be clamped
	 * @return <code>x</code> or <code>left</code> or <code>right</code>
	 */
	public static int clamp(final int left, final int right, final int x) {
		return clampBottom(left, clampTop(right, x));
	}

	/**
	 * Wraps the given <code>x</code> value to the given <code>left</code> and <code>right</code> limits. If <code>left == right</code> returns this
	 * value.
	 * @param left the left/bottom limit value
	 * @param right the right/top limit value
	 * @param x the value to be clamped
	 * @return a value in the given [<code>left</code>, <code>right</code>] range modulo equal to <code>x</code>
	 */
	public static int wrap(final int left, final int right, final int x) {
		if (left == right) { return left; }
		if (x < left) {
			final int range = right - left;
			final int d = (left - x) % range;
			return right - d;
		} else if (x > right) {
			final int range = right - left;
			final int d = (x - right) % range;
			return left + d;
		}
		return x;
	}

	/**
	 * Clamps the given <code>x</code> value to the given <code>left</code> limit.
	 * @param left the left/bottom limit value
	 * @param x the value to be clamped
	 * @return <code>x</code> or <code>left</code>
	 */
	public static float clampBottom(final float left, final float x) {
		return x < left ? left : x;
	}

	public static double clampBottom(final double left, final double x) {
		return x < left ? left : x;
	}

	/**
	 * Clamps the given <code>x</code> value to the given <code>right</code> limit.
	 * @param right the right/top limit value
	 * @param x the value to be clamped
	 * @return <code>x</code> or <code>right</code>
	 */
	public static float clampTop(final float right, final float x) {
		return x > right ? right : x;
	}

	public static double clampTop(final double right, final double x) {
		return x > right ? right : x;
	}

	/**
	 * Clamps the given <code>x</code> value to the given <code>left</code> and <code>right</code> limits.
	 * @param left the left/bottom limit value
	 * @param right the right/top limit value
	 * @param x the value to be clamped
	 * @return <code>x</code> or <code>left</code> or <code>right</code>
	 */
	public static float clamp(final float left, final float right, final float x) {
		return clampBottom(left, clampTop(right, x));
	}

	public static double clamp(final double left, final double right, final double x) {
		return clampBottom(left, clampTop(right, x));
	}

	/**
	 * Wraps the given <code>x</code> value to the given <code>left</code> and <code>right</code> limits.If <code>left == right</code> returns this
	 * value.
	 * @param left the left/bottom limit value
	 * @param right the right/top limit value
	 * @param x the value to be clamped
	 * @return a value in the given [<code>left</code>, <code>right</code>] range modulo equal to <code>x</code>
	 */
	public static float wrap(final float left, final float right, final float x) {
		if (left == right) { return left; }
		if (x < left) {
			final float range = right - left;
			final float d = (left - x) % range;
			return right - d;
		} else if (x > right) {
			final float range = right - left;
			final float d = (x - right) % range;
			return left + d;
		}
		return x;
	}

	/**
	 * Clamps the given <code>degrees</code> angle value to proper [0, 360] range.
	 * @param degrees the degrees value
	 * @return degree in the range [0, 360]
	 */
	public static float clampDegrees(final float degrees) {
		return clamp(0, 360, degrees);
	}

	/**
	 * Wraps the given <code>degrees</code> angle value to proper [0, 360] range.
	 * @param degrees the degrees value
	 * @return degree in the range [0, 360]
	 */
	public static float wrapDegrees(final float degrees) {
		return wrap(0, 360, degrees);
	}

	public static double clampUnit(final double val) {
		return clamp(0, 1, val);
	}

	/**
	 * Clamps the value to proper [0, 1] unit range.
	 * @param val the value to be clamped
	 * @return value in the range [0, 1]
	 */
	public static float clampUnit(final float val) {
		return clamp(0, 1, val);
	}

	/**
	 * Wraps the value to proper [0, 1] unit range.
	 * @param val the value to be wrapped
	 * @return value in the range [0, 1]
	 */
	public static float wrapUnit(final float val) {
		return wrap(0, 1, val);
	}

	/**
	 * Clamps the value to proper [-1, 1] extended unit range.
	 * @param val the value to be clamped
	 * @return value in the range [-1, 1]
	 */
	public static float clampExtUnit(final float val) {
		return clamp(-1, 1, val);
	}

	/**
	 * Wraps the value to proper [-1, 1] extended unit range.
	 * @param val the value to be wrapped
	 * @return value in the range [-1, 1]
	 */
	public static float wrapExtUnit(final float val) {
		return wrap(-1, 1, val);
	}

	public static void cross(final Vector3f cross, final Vector3f left, final Vector3f right) {
		cross.x = left.y * right.z - left.z * right.y;
		cross.y = left.z * right.x - left.x * right.z;
		cross.z = left.x * right.y - left.y * right.x;
	}

	public static Vector3f cross(final Vector3f left, final Vector3f right) {
		final Vector3f cross = new Vector3f();
		cross(cross, left, right);
		return cross;
	}

	public static boolean even(int x) {
		return x % 2 == 0;
	}

	public static boolean odd(int x) {
		return !even(x);
	}

	/** @return x * (1 - alpha) + alpha * y */
	public static double combine(double x, double y, double alpha) {
		return x * (1 - alpha) + alpha * y;
	}

	public static int max(Collection<Integer> values) {
		int max = Integer.MIN_VALUE;
		for (int n : values) {
			if (n > max) {
				max = n;
			}
		}
		return max;
	}

	public static boolean between(double min, double max, double x) {
		return min <= x && x <= max;
	}
}
